﻿using System;


public class MapperDefault:MemoryMapper {

    public NES nes;
    public Memory cpuMem;
    public Memory ppuMem;
    public short[] cpuMemArray;
    public ROM rom;
    public CPU cpu;
    public PPU ppu;
    public int cpuMemSize;
    public int joy1StrobeState;
    public int joy2StrobeState;
    public int joypadLastWrite;
    public bool mousePressed;
    public bool gameGenieActive;
    public int mouseX;
    public int mouseY;
    int tmp;
    long crc;


    public virtual long CRC
    {
        set
        {
        }
    }

    public virtual void init(NES nes) {

        this.nes = nes;
        this.cpuMem = nes.CpuMemory;
        this.cpuMemArray = cpuMem.mem;
        this.ppuMem = nes.PpuMemory;
        this.rom = nes.Rom;
        this.cpu = nes.Cpu;
        this.ppu = nes.Ppu;
        
        cpuMemSize = cpuMem.MemSize;
        joypadLastWrite = -1;

    }

    public void stateLoad(ByteBuffer buf) {

        // Check version:
        if (buf.readByte() == 1) {

            // Joypad stuff:
            joy1StrobeState = buf.readInt();
            joy2StrobeState = buf.readInt();
            joypadLastWrite = buf.readInt();

            // Mapper specific stuff:
            mapperInternalStateLoad(buf);

        }

    }

    public void stateSave(ByteBuffer buf) {

        // Version:
        buf.putByte((short) 1);

        // Joypad stuff:
        buf.putInt(joy1StrobeState);
        buf.putInt(joy2StrobeState);
        buf.putInt(joypadLastWrite);

        // Mapper specific stuff:
        mapperInternalStateSave(buf);

    }

    public virtual void mapperInternalStateLoad(ByteBuffer buf) {

        buf.putByte((short) joy1StrobeState);
        buf.putByte((short) joy2StrobeState);
        buf.putByte((short) joypadLastWrite);

    }

    public virtual void mapperInternalStateSave(ByteBuffer buf) {

        joy1StrobeState = buf.readByte();
        joy2StrobeState = buf.readByte();
        joypadLastWrite = buf.readByte();

    }

    public void setGameGenieState(bool enable) {
        gameGenieActive = enable;
    }

    public bool getGameGenieState() {
        return gameGenieActive;
    }

    public virtual void write(int address, short value) {

        if (address < 0x2000) {

            // Mirroring of RAM:
            cpuMem.mem[address & 0x7FF] = value;

        } else if (address > 0x4017) {

            cpuMem.mem[address] = value;
            if (address >= 0x6000 && address < 0x8000) {

                // Write to SaveRAM. Store in file:
                if (rom != null) {
                    rom.writeBatteryRam(address, value);
                }

            }

        } else if (address > 0x2007 && address < 0x4000) {

            regWrite(0x2000 + (address & 0x7), value);

        } else {

            regWrite(address, value);

        }

    }

    public virtual void writelow(int address, short value) {

        if (address < 0x2000) {
            // Mirroring of RAM:
            cpuMem.mem[address & 0x7FF] = value;

        } else if (address > 0x4017) {
            cpuMem.mem[address] = value;

        } else if (address > 0x2007 && address < 0x4000) {
            regWrite(0x2000 + (address & 0x7), value);

        } else {
            regWrite(address, value);
        }

    }

    public virtual short load(int address)
    {

        // Game Genie codes active?
        if (gameGenieActive) {
            if (nes.gameGenie.addressMatch[address]) {

                tmp = nes.gameGenie.getCodeIndex(address);

                // Check the code type:
                if (nes.gameGenie.getCodeType(tmp) == GameGenie.TYPE_6CHAR) {

                    // Return the code value:
                    return (short) nes.gameGenie.getCodeValue(tmp);

                } else {

                    // Check whether the actual value equals the compare value:
                    if (cpuMemArray[address] == nes.gameGenie.getCodeCompare(tmp)) {

                        // The values match, so use the supplied game genie value:
                        return (short) nes.gameGenie.getCodeValue(tmp);

                    }

                }
            }
        }

        // Wrap around:
        address &= 0xFFFF;

        // Check address range:
        if (address > 0x4017) {

            // ROM:
            return cpuMemArray[address];

        } else if (address >= 0x2000) {

            // I/O Ports.
            return regLoad(address);

        } else {

            // RAM (mirrored)
            return cpuMemArray[address & 0x7FF];

        }

    }

    public short regLoad(int address) {

        switch (address >> 12) { // use fourth nibble (0xF000)

            case 0: {
                break;
            }
            case 1: {
                break;
            }
            case 2: 
                // Fall through to case 3
            
            case 3: {

                // PPU Registers
                switch (address & 0x7) {
                    case 0x0: {

                        // 0x2000:
                        // PPU Control Register 1.
                        // (the value is stored both
                        // in main memory and in the
                        // PPU as flags):
                        // (not in the real NES)
                        return cpuMem.mem[0x2000];

                    }
                    case 0x1: {

                        // 0x2001:
                        // PPU Control Register 2.
                        // (the value is stored both
                        // in main memory and in the
                        // PPU as flags):
                        // (not in the real NES)
                        return cpuMem.mem[0x2001];

                    }
                    case 0x2: {

                        // 0x2002:
                        // PPU Status Register.
                        // The value is stored in
                        // main memory in addition
                        // to as flags in the PPU.
                        // (not in the real NES)
                        return ppu.readStatusRegister();

                    }
                    case 0x3: {
                        return 0;
                    }
                    case 0x4: {

                        // 0x2004:
                        // Sprite Memory read.
                        return ppu.sramLoad();

                    }
                    case 0x5: {
                        return 0;
                    }
                    case 0x6: {
                        return 0;
                    }
                    case 0x7: {

                        // 0x2007:
                        // VRAM read:
                        return ppu.vramLoad();

                    }
                }
                break;

            }
            case 4: {


                // Sound+Joypad registers

                switch (address - 0x4015) {
                    case 0: {

                        // 0x4015:
                        // Sound channel enable, DMC Status
                        return nes.Papu.readReg(address);

                    }
                    case 1: {

                        // 0x4016:
                        // Joystick 1 + Strobe
                        return joy1Read();

                    }
                    case 2: {

                        // 0x4017:
                        // Joystick 2 + Strobe
                        if (mousePressed && nes.ppu != null && nes.ppu.buffer != null) {

                            // Check for white pixel nearby:

                            int sx, sy, ex, ey, w;
                            sx = Math.Max(0, mouseX - 4);
                            ex = Math.Min(256, mouseX + 4);
                            sy = Math.Max(0, mouseY - 4);
                            ey = Math.Min(240, mouseY + 4);
                            w = 0;

                            for (int y = sy; y < ey; y++) {
                                for (int x = sx; x < ex; x++) {
                                    if ((nes.ppu.buffer[(y << 8) + x] & 0xFFFFFF) == 0xFFFFFF) {
                                        w = 0x1 << 3;
                                        break;
                                    }
                                }
                            }

                            w |= (mousePressed ? (0x1 << 4) : 0);
                            return (short) (joy2Read() | w);

                        } else {
                            return joy2Read();
                        }

                    }
                }

                break;

            }
        }

        return 0;

    }

    public void regWrite(int address, short value) {

        switch (address) {
            case 0x2000: {

                // PPU Control register 1
                cpuMem.write(address, value);
                ppu.updateControlReg1(value);
                break;

            }
            case 0x2001: {

                // PPU Control register 2
                cpuMem.write(address, value);
                ppu.updateControlReg2(value);
                break;

            }
            case 0x2003: {

                // Set Sprite RAM address:
                ppu.writeSRAMAddress(value);
                break;

            }
            case 0x2004: {

                // Write to Sprite RAM:
                ppu.sramWrite(value);
                break;

            }
            case 0x2005: {

                // Screen Scroll offsets:
                ppu.scrollWrite(value);
                break;

            }
            case 0x2006: {

                // Set VRAM address:
                ppu.writeVRAMAddress(value);
                break;

            }
            case 0x2007: {

                // Write to VRAM:
                ppu.vramWrite(value);
                break;

            }
            case 0x4014: {

                // Sprite Memory DMA Access
                ppu.sramDMA(value);
                break;

            }
            case 0x4015: {

                // Sound Channel Switch, DMC Status
                nes.Papu.writeReg(address, value);
                break;

            }
            case 0x4016: {

                ////System.out.println("joy strobe write "+value);

                // Joystick 1 + Strobe
                if (value == 0 && joypadLastWrite == 1) {
                    ////System.out.println("Strobes reset.");
                    joy1StrobeState = 0;
                    joy2StrobeState = 0;
                }
                joypadLastWrite = value;
                break;

            }
            case 0x4017: {

                // Sound channel frame sequencer:
                nes.papu.writeReg(address, value);
                break;

            }
            default: {

                // Sound registers
                ////System.out.println("write to sound reg");
                if (address >= 0x4000 && address <= 0x4017) {
                    nes.Papu.writeReg(address, value);
                }
                break;

            }
        }

    }

    public short joy1Read() {

        InputHandler @in = nes.Gui.Joy1;
        short ret;

        switch (joy1StrobeState) {
            case 0:
                ret = @in.getKeyState(InputHandler_Fields.KEY_A);
                break;
            case 1:
                ret = @in.getKeyState(InputHandler_Fields.KEY_B);
                break;
            case 2:
                ret = @in.getKeyState(InputHandler_Fields.KEY_SELECT);
                break;
            case 3:
                ret = @in.getKeyState(InputHandler_Fields.KEY_START);
                break;
            case 4:
                ret = @in.getKeyState(InputHandler_Fields.KEY_UP);
                break;
            case 5:
                ret = @in.getKeyState(InputHandler_Fields.KEY_DOWN);
                break;
            case 6:
                ret = @in.getKeyState(InputHandler_Fields.KEY_LEFT);
                break;
            case 7:
                ret = @in.getKeyState(InputHandler_Fields.KEY_RIGHT);
                break;
            case 8:
            case 9:
            case 10:
            case 11:
            case 12:
            case 13:
            case 14:
            case 15:
            case 16:
            case 17:
            case 18:
                ret = (short) 0;
                break;
            case 19:
                ret = (short) 1;
                break;
            default:
                ret = 0;
                break;
        }

        joy1StrobeState++;
        if (joy1StrobeState == 24) {
            joy1StrobeState = 0;
        }

        return ret;

    }

    public short joy2Read() {
        InputHandler @in = nes.Gui.Joy2;
        int st = joy2StrobeState;

        joy2StrobeState++;
        if (joy2StrobeState == 24) {
            joy2StrobeState = 0;
        }

        if (st == 0) {
            return @in.getKeyState(InputHandler_Fields.KEY_A);
        } else if (st == 1) {
            return @in.getKeyState(InputHandler_Fields.KEY_B);
        } else if (st == 2) {
            return @in.getKeyState(InputHandler_Fields.KEY_SELECT);
        } else if (st == 3) {
            return @in.getKeyState(InputHandler_Fields.KEY_START);
        } else if (st == 4) {
            return @in.getKeyState(InputHandler_Fields.KEY_UP);
        } else if (st == 5) {
            return @in.getKeyState(InputHandler_Fields.KEY_DOWN);
        } else if (st == 6) {
            return @in.getKeyState(InputHandler_Fields.KEY_LEFT);
        } else if (st == 7) {
            return @in.getKeyState(InputHandler_Fields.KEY_RIGHT);
        } else if (st == 16) {
            return (short) 0;
        } else if (st == 17) {
            return (short) 0;
        } else if (st == 18) {
            return (short) 1;
        } else if (st == 19) {
            return (short) 0;
        } else {
            return 0;
        }
    }

    public virtual void loadROM(ROM rom) {

        if (!rom.Valid || rom.RomBankCount < 1) {
            //System.out.println("NoMapper: Invalid ROM! Unable to load.");
            return;
        }

        // Load ROM into memory:
        loadPRGROM();

        // Load CHR-ROM:
        loadCHRROM();

        // Load Battery RAM (if present):
        loadBatteryRam();

        // Reset IRQ:
        //nes.getCpu().doResetInterrupt();
        nes.Cpu.requestIrq(CPU.IRQ_RESET);

    }

    protected void loadPRGROM() {

        if (rom.RomBankCount > 1) {
            // Load the two first banks into memory.
            loadRomBank(0, 0x8000);
            loadRomBank(1, 0xC000);
        } else {
            // Load the one bank into both memory locations:
            loadRomBank(0, 0x8000);
            loadRomBank(0, 0xC000);
        }

    }

    protected void loadCHRROM() {

        ////System.out.println("Loading CHR ROM..");

        if (rom.VromBankCount > 0) {
            if (rom.VromBankCount == 1) {
                loadVromBank(0, 0x0000);
                loadVromBank(0, 0x1000);
            } else {
                loadVromBank(0, 0x0000);
                loadVromBank(1, 0x1000);
            }
        } else {
            //System.out.println("There aren't any CHR-ROM banks..");
        }

    }

    public void loadBatteryRam() {

        if (rom.batteryRam) {

            short[] ram = rom.BatteryRam;
            if (ram != null && ram.Length == 0x2000) {

                // Load Battery RAM into memory:
                Array.Copy(ram, 0, nes.cpuMem.mem, 0x6000, 0x2000);

            }

        }

    }

    protected void loadRomBank(int bank, int address) {

        // Loads a ROM bank into the specified address.
        bank %= rom.RomBankCount;
        short[] data = rom.getRomBank(bank);
        //cpuMem.write(address,data,data.length);
        Array.Copy(rom.getRomBank(bank), 0, cpuMem.mem, address, 16384);

    }

    protected void loadVromBank(int bank, int address) {

        if (rom.VromBankCount == 0) {
            return;
        }
        ppu.triggerRendering();

        Array.Copy(rom.getVromBank(bank % rom.VromBankCount), 0, nes.ppuMem.mem, address, 4096);

        Tile[] vromTile = rom.getVromBankTiles(bank % rom.VromBankCount);
        Array.Copy(vromTile, 0, ppu.ptTile, address >> 4, 256);

    }

    protected void load32kRomBank(int bank, int address) {

        loadRomBank((bank * 2) % rom.RomBankCount, address);
        loadRomBank((bank * 2 + 1) % rom.RomBankCount, address + 16384);

    }

    protected void load8kVromBank(int bank4kStart, int address) {

        if (rom.VromBankCount == 0) {
            return;
        }
        ppu.triggerRendering();

        loadVromBank((bank4kStart) % rom.VromBankCount, address);
        loadVromBank((bank4kStart + 1) % rom.VromBankCount, address + 4096);

    }

    protected void load1kVromBank(int bank1k, int address) {

        if (rom.VromBankCount == 0) {
            return;
        }
        ppu.triggerRendering();

        int bank4k = (bank1k / 4) % rom.VromBankCount;
        int bankoffset = (bank1k % 4) * 1024;
        Array.Copy(rom.getVromBank(bank4k), 0, nes.ppuMem.mem, bankoffset, 1024);

        // Update tiles:
        Tile[] vromTile = rom.getVromBankTiles(bank4k);
        int baseIndex = address >> 4;
        for (int i = 0; i < 64; i++) {
            ppu.ptTile[baseIndex + i] = vromTile[((bank1k % 4) << 6) + i];
        }

    }

    protected void load2kVromBank(int bank2k, int address) {

        if (rom.VromBankCount == 0) {
            return;
        }
        ppu.triggerRendering();

        int bank4k = (bank2k / 2) % rom.VromBankCount;
        int bankoffset = (bank2k % 2) * 2048;
        Array.Copy(rom.getVromBank(bank4k), bankoffset, nes.ppuMem.mem, address, 2048);

        // Update tiles:
        Tile[] vromTile = rom.getVromBankTiles(bank4k);
        int baseIndex = address >> 4;
        for (int i = 0; i < 128; i++) {
            ppu.ptTile[baseIndex + i] = vromTile[((bank2k % 2) << 7) + i];
        }

    }

    protected void load8kRomBank(int bank8k, int address) {

        int bank16k = (bank8k / 2) % rom.RomBankCount;
        int offset = (bank8k % 2) * 8192;

        short[] bank = rom.getRomBank(bank16k);
        cpuMem.write(address, bank, offset, 8192);

    }

    public virtual void clockIrqCounter() {
        // Does nothing. This is used by the MMC3 mapper.
    }

    public virtual void latchAccess(int address)
    {
        // Does nothing. This is used by MMC2.
    }

    public virtual int syncV()
    {
        return 0;
    }

    public virtual int syncH(int scanline) {
        return 0;
      }

    public virtual void setCRC(long crc)
    {
    }

    public void setMouseState(bool pressed, int x, int y) {

        mousePressed = pressed;
        mouseX = x;
        mouseY = y;

    }

    public virtual void reset() {

        joy1StrobeState = 0;
        joy2StrobeState = 0;
        joypadLastWrite = 0;
        mousePressed = false;

    }

    public void destroy() {

        nes = null;
        cpuMem = null;
        ppuMem = null;
        rom = null;
        cpu = null;
        ppu = null;

    }
}

public class MapperDefault2 : MemoryMapper
{
    public CPU cpu;
    public Memory cpuMem;
    public short[] cpuMemArray;
    public int cpuMemSize;
    internal long crc;
    public bool gameGenieActive;
    public int joy1StrobeState;
    public int joy2StrobeState;
    public int joypadLastWrite;
    public bool mousePressed;
    public int mouseX;
    public int mouseY;
    public NES nes;
    public PPU ppu;
    public Memory ppuMem;
    public ROM rom;
    internal int tmp;

    public virtual void clockIrqCounter()
    {
    }

    public virtual void destroy()
    {
        this.nes = null;
        this.cpuMem = null;
        this.ppuMem = null;
        this.rom = null;
        this.cpu = null;
        this.ppu = null;
    }

    public virtual bool getGameGenieState()
    {
        return this.gameGenieActive;
    }

    public virtual void init(NES nes)
    {
        this.nes = nes;
        this.cpuMem = nes.CpuMemory;
        this.cpuMemArray = this.cpuMem.mem;
        this.ppuMem = nes.PpuMemory;
        this.rom = nes.Rom;
        this.cpu = nes.Cpu;
        this.ppu = nes.Ppu;
        this.cpuMemSize = this.cpuMem.MemSize;
        this.joypadLastWrite = -1;
    }

    public virtual short joy1Read()
    {
        short num;
        InputHandler handler = this.nes.Gui.Joy1;
        switch (this.joy1StrobeState)
        {
            case 0:
                num = handler.getKeyState(InputHandler_Fields.KEY_A);
                break;

            case 1:
                num = handler.getKeyState(InputHandler_Fields.KEY_B);
                break;

            case 2:
                num = handler.getKeyState(InputHandler_Fields.KEY_SELECT);
                break;

            case 3:
                num = handler.getKeyState(InputHandler_Fields.KEY_START);
                break;

            case 4:
                num = handler.getKeyState(InputHandler_Fields.KEY_UP);
                break;

            case 5:
                num = handler.getKeyState(InputHandler_Fields.KEY_DOWN);
                break;

            case 6:
                num = handler.getKeyState(InputHandler_Fields.KEY_LEFT);
                break;

            case 7:
                num = handler.getKeyState(InputHandler_Fields.KEY_RIGHT);
                break;

            case 8:
            case 9:
            case 10:
            case 11:
            case 12:
            case 13:
            case 14:
            case 15:
            case 0x10:
            case 0x11:
            case 0x12:
                num = 0;
                break;

            case 0x13:
                num = 1;
                break;

            default:
                num = 0;
                break;
        }
        this.joy1StrobeState++;
        if (this.joy1StrobeState == 0x18)
        {
            this.joy1StrobeState = 0;
        }
        return num;
    }

    public virtual short joy2Read()
    {
        InputHandler handler = this.nes.Gui.Joy2;
        int num = this.joy2StrobeState;
        this.joy2StrobeState++;
        if (this.joy2StrobeState == 0x18)
        {
            this.joy2StrobeState = 0;
        }
        switch (num)
        {
            case 0:
                return handler.getKeyState(InputHandler_Fields.KEY_A);

            case 1:
                return handler.getKeyState(InputHandler_Fields.KEY_B);

            case 2:
                return handler.getKeyState(InputHandler_Fields.KEY_SELECT);

            case 3:
                return handler.getKeyState(InputHandler_Fields.KEY_START);

            case 4:
                return handler.getKeyState(InputHandler_Fields.KEY_UP);

            case 5:
                return handler.getKeyState(InputHandler_Fields.KEY_DOWN);

            case 6:
                return handler.getKeyState(InputHandler_Fields.KEY_LEFT);

            case 7:
                return handler.getKeyState(InputHandler_Fields.KEY_RIGHT);

            case 0x10:
                return 0;

            case 0x11:
                return 0;

            case 0x12:
                return 1;

            case 0x13:
                return 0;
        }
        return 0;
    }

    public virtual void latchAccess(int address)
    {
    }

    public virtual short load(int address)
    {
        if (this.gameGenieActive && this.nes.gameGenie.addressMatch[address])
        {
            this.tmp = this.nes.gameGenie.getCodeIndex(address);
            if (this.nes.gameGenie.getCodeType(this.tmp) == GameGenie.TYPE_6CHAR)
            {
                return (short) this.nes.gameGenie.getCodeValue(this.tmp);
            }
            if (this.cpuMemArray[address] == this.nes.gameGenie.getCodeCompare(this.tmp))
            {
                return (short) this.nes.gameGenie.getCodeValue(this.tmp);
            }
        }
        address &= 0xffff;
        if (address > 0x4017)
        {
            return this.cpuMemArray[address];
        }
        if (address >= 0x2000)
        {
            return this.regLoad(address);
        }
        return this.cpuMemArray[address & 0x7ff];
    }

    protected internal virtual void load1kVromBank(int bank1k, int address)
    {
        if (this.rom.VromBankCount != 0)
        {
            this.ppu.triggerRendering();
            int bank = (bank1k / 4) % this.rom.VromBankCount;
            int destinationIndex = (bank1k % 4) * 0x400;
            Array.Copy(this.rom.getVromBank(bank), 0, this.nes.ppuMem.mem, destinationIndex, 0x400);
            Tile[] tileArray = this.rom.getVromBankTiles(bank);
            int num3 = address >> 4;
            for (int i = 0; i < 0x40; i++)
            {
                this.ppu.ptTile[num3 + i] = tileArray[((bank1k % 4) << 6) + i];
            }
        }
    }

    protected internal virtual void load2kVromBank(int bank2k, int address)
    {
        if (this.rom.VromBankCount != 0)
        {
            this.ppu.triggerRendering();
            int bank = (bank2k / 2) % this.rom.VromBankCount;
            int sourceIndex = (bank2k % 2) * 0x800;
            Array.Copy(this.rom.getVromBank(bank), sourceIndex, this.nes.ppuMem.mem, address, 0x800);
            Tile[] tileArray = this.rom.getVromBankTiles(bank);
            int num3 = address >> 4;
            for (int i = 0; i < 0x80; i++)
            {
                this.ppu.ptTile[num3 + i] = tileArray[((bank2k % 2) << 7) + i];
            }
        }
    }

    protected internal virtual void load32kRomBank(int bank, int address)
    {
        this.loadRomBank((bank * 2) % this.rom.RomBankCount, address);
        this.loadRomBank(((bank * 2) + 1) % this.rom.RomBankCount, address + 0x4000);
    }

    protected internal virtual void load8kRomBank(int bank8k, int address)
    {
        int bank = (bank8k / 2) % this.rom.RomBankCount;
        int arrayoffset = (bank8k % 2) * 0x2000;
        short[] array = this.rom.getRomBank(bank);
        this.cpuMem.write(address, array, arrayoffset, 0x2000);
    }

    protected internal virtual void load8kVromBank(int bank4kStart, int address)
    {
        if (this.rom.VromBankCount != 0)
        {
            this.ppu.triggerRendering();
            this.loadVromBank(bank4kStart % this.rom.VromBankCount, address);
            this.loadVromBank((bank4kStart + 1) % this.rom.VromBankCount, address + 0x1000);
        }
    }

    public virtual void loadBatteryRam()
    {
        if (this.rom.batteryRam)
        {
            short[] batteryRam = this.rom.BatteryRam;
            if ((batteryRam != null) && (batteryRam.Length == 0x2000))
            {
                Array.Copy(batteryRam, 0, this.nes.cpuMem.mem, 0x6000, 0x2000);
            }
        }
    }

    protected internal virtual void loadCHRROM()
    {
        if (this.rom.VromBankCount > 0)
        {
            if (this.rom.VromBankCount == 1)
            {
                this.loadVromBank(0, 0);
                this.loadVromBank(0, 0x1000);
            }
            else
            {
                this.loadVromBank(0, 0);
                this.loadVromBank(1, 0x1000);
            }
        }
    }

    protected internal virtual void loadPRGROM()
    {
        if (this.rom.RomBankCount > 1)
        {
            this.loadRomBank(0, 0x8000);
            this.loadRomBank(1, 0xc000);
        }
        else
        {
            this.loadRomBank(0, 0x8000);
            this.loadRomBank(0, 0xc000);
        }
    }

    public virtual void loadROM(ROM rom)
    {
        if (rom.Valid && (rom.RomBankCount >= 1))
        {
            this.loadPRGROM();
            this.loadCHRROM();
            this.loadBatteryRam();
            this.nes.Cpu.requestIrq(2);
        }
    }

    protected internal virtual void loadRomBank(int bank, int address)
    {
        bank = bank % this.rom.RomBankCount;
        this.rom.getRomBank(bank);
        Array.Copy(this.rom.getRomBank(bank), 0, this.cpuMem.mem, address, 0x4000);
    }

    protected internal virtual void loadVromBank(int bank, int address)
    {
        if (this.rom.VromBankCount != 0)
        {
            this.ppu.triggerRendering();
            Array.Copy(this.rom.getVromBank(bank % this.rom.VromBankCount), 0, this.nes.ppuMem.mem, address, 0x1000);
            Array.Copy(this.rom.getVromBankTiles(bank % this.rom.VromBankCount), 0, this.ppu.ptTile, address >> 4, 0x100);
        }
    }

    public virtual void mapperInternalStateLoad(ByteBuffer buf)
    {
        buf.putByte((short) this.joy1StrobeState);
        buf.putByte((short) this.joy2StrobeState);
        buf.putByte((short) this.joypadLastWrite);
    }

    public virtual void mapperInternalStateSave(ByteBuffer buf)
    {
        this.joy1StrobeState = buf.readByte();
        this.joy2StrobeState = buf.readByte();
        this.joypadLastWrite = buf.readByte();
    }

    public virtual short regLoad(int address)
    {
        switch ((address >> 12))
        {
            case 2:
            case 3:
                switch ((address & 7))
                {
                    case 0:
                        return this.cpuMem.mem[0x2000];

                    case 1:
                        return this.cpuMem.mem[0x2001];

                    case 2:
                        return this.ppu.readStatusRegister();

                    case 3:
                        return 0;

                    case 4:
                        return this.ppu.sramLoad();

                    case 5:
                        return 0;

                    case 6:
                        return 0;

                    case 7:
                        return this.ppu.vramLoad();
                }
                break;

            case 4:
                switch (address)
                {
                    case 0x4015:
                        return this.nes.Papu.readReg(address);

                    case 0x4016:
                        return this.joy1Read();

                    case 0x4017:
                    {
                        if ((!this.mousePressed || (this.nes.ppu == null)) || (this.nes.ppu.buffer == null))
                        {
                            return this.joy2Read();
                        }
                        int num = Math.Max(0, this.mouseX - 4);
                        int num3 = Math.Min(0x100, this.mouseX + 4);
                        int num2 = Math.Max(0, this.mouseY - 4);
                        int num4 = Math.Min(240, this.mouseY + 4);
                        int num5 = 0;
                        for (int i = num2; i < num4; i++)
                        {
                            for (int j = num; j < num3; j++)
                            {
                                if ((this.nes.ppu.buffer[(i << 8) + j] & 0xffffff) == 0xffffff)
                                {
                                    num5 = 8;
                                    break;
                                }
                            }
                        }
                        num5 |= this.mousePressed ? 0x10 : 0;
                        return (short) (this.joy2Read() | num5);
                    }
                }
                break;
        }
        return 0;
    }

    public virtual void regWrite(int address, short value_Renamed)
    {
        switch (address)
        {
            case 0x2000:
                this.cpuMem.write(address, value_Renamed);
                this.ppu.updateControlReg1(value_Renamed);
                return;

            case 0x2001:
                this.cpuMem.write(address, value_Renamed);
                this.ppu.updateControlReg2(value_Renamed);
                return;

            case 0x2003:
                this.ppu.writeSRAMAddress(value_Renamed);
                return;

            case 0x2004:
                this.ppu.sramWrite(value_Renamed);
                return;

            case 0x2005:
                this.ppu.scrollWrite(value_Renamed);
                return;

            case 0x2006:
                this.ppu.writeVRAMAddress(value_Renamed);
                return;

            case 0x2007:
                this.ppu.vramWrite(value_Renamed);
                return;

            case 0x4014:
                this.ppu.sramDMA(value_Renamed);
                return;

            case 0x4015:
                this.nes.Papu.writeReg(address, value_Renamed);
                return;

            case 0x4016:
                if ((value_Renamed == 0) && (this.joypadLastWrite == 1))
                {
                    this.joy1StrobeState = 0;
                    this.joy2StrobeState = 0;
                }
                this.joypadLastWrite = value_Renamed;
                return;

            case 0x4017:
                this.nes.papu.writeReg(address, value_Renamed);
                return;
        }
        if ((address >= 0x4000) && (address <= 0x4017))
        {
            this.nes.Papu.writeReg(address, value_Renamed);
        }
    }

    public virtual void reset()
    {
        this.joy1StrobeState = 0;
        this.joy2StrobeState = 0;
        this.joypadLastWrite = 0;
        this.mousePressed = false;
    }

    public virtual void setGameGenieState(bool enable)
    {
        this.gameGenieActive = enable;
    }

    public virtual void setMouseState(bool pressed, int x, int y)
    {
        this.mousePressed = pressed;
        this.mouseX = x;
        this.mouseY = y;
    }

    public virtual void stateLoad(ByteBuffer buf)
    {
        if (buf.readByte() == 1)
        {
            this.joy1StrobeState = buf.readInt();
            this.joy2StrobeState = buf.readInt();
            this.joypadLastWrite = buf.readInt();
            this.mapperInternalStateLoad(buf);
        }
    }

    public virtual void stateSave(ByteBuffer buf)
    {
        buf.putByte(1);
        buf.putInt(this.joy1StrobeState);
        buf.putInt(this.joy2StrobeState);
        buf.putInt(this.joypadLastWrite);
        this.mapperInternalStateSave(buf);
    }

    public virtual int syncH(int scanline)
    {
        return 0;
    }

    public virtual int syncV()
    {
        return 0;
    }

    public virtual void write(int address, short value_Renamed)
    {
        if (address < 0x2000)
        {
            this.cpuMem.mem[address & 0x7ff] = value_Renamed;
        }
        else if (address > 0x4017)
        {
            this.cpuMem.mem[address] = value_Renamed;
            if (((address >= 0x6000) && (address < 0x8000)) && (this.rom != null))
            {
                this.rom.writeBatteryRam(address, value_Renamed);
            }
        }
        else if ((address > 0x2007) && (address < 0x4000))
        {
            this.regWrite(0x2000 + (address & 7), value_Renamed);
        }
        else
        {
            this.regWrite(address, value_Renamed);
        }
    }

    public virtual void writelow(int address, short value_Renamed)
    {
        if (address < 0x2000)
        {
            this.cpuMem.mem[address & 0x7ff] = value_Renamed;
        }
        else if (address > 0x4017)
        {
            this.cpuMem.mem[address] = value_Renamed;
        }
        else if ((address > 0x2007) && (address < 0x4000))
        {
            this.regWrite(0x2000 + (address & 7), value_Renamed);
        }
        else
        {
            this.regWrite(address, value_Renamed);
        }
    }

    public virtual long CRC
    {
        set
        {
        }
    }
}

